package cn.ltgodm.sandbox.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.ltgodm.sandbox.exception.ExecuteException;
import cn.ltgodm.sandbox.model.dto.ExecuteCodeRequest;
import cn.ltgodm.sandbox.model.dto.ExecuteCodeResponse;
import cn.ltgodm.sandbox.model.dto.ExecuteMessage;
import cn.ltgodm.sandbox.service.CodeSandboxTemplate;
import cn.ltgodm.sandbox.utils.ProcessUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.ExecCreateCmd;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.ExecStartCmd;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.api.model.StreamType;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author ltgodm
 * @date 2024-03-07 10:25:28
 */
@Slf4j
@Service
public class JSShellSandboxServiceImpl extends CodeSandboxTemplate {
    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
        try{
        //初始化参数
        List<String> inputList = executeCodeRequest.getInputList();
        String code = executeCodeRequest.getCode();
        String language = executeCodeRequest.getLanguage();

        //1.获取dockerClient连接
        DockerClient dockerClient = super.getDockerClient(this.dockerHost);

        //2.启动容器
        String containerId = super.createContainer(language, languageConfigs, dockerClient);

        //3.运行shell脚本 上传文件到docker容器中
        //2.1 判断shell脚本是否存在，如果不存在上传shell脚本
        //2.2 构造出容器保存的路径
        //2.3 将构造出来的路径通过命令传参，运行shell脚本
        String userCodeFilePath = saveUserCodeFile(containerId, dockerClient, code);

        //4.运行代码
        String[] runCode = {"node", StrUtil.format("{}/{}", userCodeFilePath, className.concat(".js"))};
        List<ExecuteMessage> executeMessageList = super.runCodeFile(inputList, dockerClient, containerId, runCode);

        //5.清理文件
        //5.1 使用shell脚本删除代码
        removeUserCodeFile(containerId, dockerClient, userCodeFilePath);

        //6.构造返回结果
        ExecuteCodeResponse response = super.getResponse(executeMessageList);
        return response;
        } catch (Exception e) {
            return ProcessUtil.getErrorResponse(e);
        }
    }

    /**
     * 保存用户文件
     *
     * @param containerId  容器id
     * @param dockerClient dockerClient连接
     * @param code         用户代码
     * @return 代码保存目录
     */
    private String saveUserCodeFile(String containerId, DockerClient dockerClient, String code) {
        //1.创建执行脚本
        //生成一个UUID路径
        String uuid = IdUtil.fastSimpleUUID();
        String userCodeFilePath = StrUtil.format("/{}/{}", globalCodePath, uuid);
        String fileName = className.concat(".js");
        String[] cmdArray = new String[]{"sh", StrUtil.format("/{}/saveUserFile.sh", globalCodePath), userCodeFilePath, fileName, code};
        ExecCreateCmd execCreateCmd = dockerClient.execCreateCmd(containerId);
        ExecCreateCmdResponse exec = execCreateCmd
                .withCmd(cmdArray)
                .withAttachStdin(true)
                .withAttachStderr(true)
                .withAttachStdout(true)
                .exec();
        String execId = exec.getId();

        //2.获取执行信息
        ExecStartResultCallback callback = new ExecStartResultCallback() {
            @Override
            public void onNext(Frame frame) {
                StreamType streamType = frame.getStreamType();
                String msg = new String(frame.getPayload());
                if (streamType.equals(StreamType.STDERR)) {
                    //错误
                    log.error("保存用户代码文件出错：{}", msg);
                    throw new ExecuteException("保存用户代码文件发生异常：" + msg);
                } else {
                    //成功
                    log.info("保存用户代码文件成功：{}", msg);
                }
            }
        };

        //3.执行
        ExecStartCmd execStartCmd = dockerClient.execStartCmd(execId);
        try {
            execStartCmd.exec(callback).awaitCompletion();
        } catch (InterruptedException e) {
            throw new ExecuteException("保存用户代码文件发生异常：" + e.getMessage());
        } finally {
            execStartCmd.close();
            execCreateCmd.close();
        }
        //4.返回
        return userCodeFilePath;
    }

    /**
     * 删除用户文件
     *
     * @param containerId  容器id
     * @param dockerClient dockerClient连接
     */
    private void removeUserCodeFile(String containerId, DockerClient dockerClient, String userCodeFilePath) {
        //1.创建执行脚本
        String[] cmdArray = new String[]{"sh", StrUtil.format("/{}/removeUserFile.sh", globalCodePath), userCodeFilePath};
        ExecCreateCmdResponse exec = dockerClient.execCreateCmd(containerId)
                .withCmd(cmdArray)
                .withAttachStdin(true)
                .withAttachStderr(true)
                .withAttachStdout(true)
                .exec();
        String execId = exec.getId();

        //2.获取执行信息
        ExecStartResultCallback callback = new ExecStartResultCallback() {
            @Override
            public void onNext(Frame frame) {
                StreamType streamType = frame.getStreamType();
                String msg = new String(frame.getPayload());
                if (streamType.equals(StreamType.STDERR)) {
                    //错误
                    log.error("删除用户代码文件出错：{}", msg);
                    throw new ExecuteException("删除用户代码文件发生异常：" + msg);
                } else {
                    //成功
                    log.info("删除用户代码文件成功：{}", msg);
                }
            }
        };

        //3.执行
        try {
            dockerClient.execStartCmd(execId).exec(callback).awaitCompletion();
        } catch (InterruptedException e) {
            throw new ExecuteException("删除用户代码文件发生异常：" + e.getMessage());
        }
    }


}
